# This is a sample Python script.
import binascii
import sys
import time

import ft4222
from PySide6.QtCore import Signal, Qt, QTimer
from PySide6.QtWidgets import QDialog, QApplication, QMessageBox
from ft4222 import Dir, Port, Trigger
from loguru import logger
from ft4222Dev.ft4222UI_ui import Ui_Ft4222Dialog


# Press Shift+F10 to execute it or replace it with your code.
# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings.
class Ft4222Window(Ui_Ft4222Dialog, QDialog):
    ft4222Signal = Signal(dict)  # 子界面类创建信号用来绑定主界面类的函数方法
    # 设备对象
    operationMode = None
    myFt4222A: ft4222.FT4222 = None
    myFt4222B: ft4222.FT4222 = None
    myFt4222C: ft4222.FT4222 = None
    myFt4222D: ft4222.FT4222 = None
    # try:
    #     myFt4222A = ft4222.FT4222()
    #     myFt4222B = ft4222.FT4222()
    # except Exception as err:
    #     pass
    # 检查连接状态周期
    checkFt4222Delay = 1000
    # 设备数据统计
    dataNumReceived = 0
    dataNumSended = 0
    # 检测所有存在的设备，将信息存储在字典中，已经连接过的设备：{"connected":"<desc>"}
    # serialPortDict = {"connected": "<desc>"}
    outGpioLevel = True
    inGpioLevel = True
    ctrlPortList = [2, 3]
    # 支持的交互类型
    supportType = ['send_hex', 'read_hex', 'read_ascii', 'ctrl_gpio2', 'read_gpio', ]

    def CleanupSelf(self):
        """
        清理当前设备并关闭，给其他其他程序调用
        :return:
        """
        try:
            # 停止并注销定时器
            self.checkFt4222Timer.stop()
            self.checkFt4222Timer.deleteLater()
            self.ctrlGpioXTimer.stop()
            self.ctrlGpioXTimer.deleteLater()
            # 关闭当前窗口
            self.close()
            print("Ft4222Window.__del__.ok")
        except Exception as err:
            print("Ft4222Window.__del__.error", err)

    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.SignalConnectInit()
        self.TimerInit()
        self.WatchFt4222Fun()

    def SignalConnectInit(self):
        """
        连接 信号 与 槽
        """
        # 连接 QComboBox
        # 连接 QPushButton
        self.btnFt4222Watch.clicked.connect(self.WatchFt4222Fun)
        self.btnCtrlFt4222.clicked.connect(self.CtrlFt4222Fun)
        self.btnCtrlGpioReversal.clicked.connect(self.CtrlGpioReversalFun)
        self.btnCtrlGpioSingle.clicked.connect(self.CtrlGpioSingleFun)
        self.btnLED1Blink.clicked.connect(self.LED1BlinkFun)
        self.buttonBox.accepted.connect(self.close)
        self.buttonBox.rejected.connect(self.close)

    def TimerInit(self):
        """
        定时器初始化
        """
        # 定时监控监测设备连接状态，周期为 1000ms
        self.checkFt4222Timer = QTimer()
        self.checkFt4222Timer.timeout.connect(self.CheckFt4222StatusFun)
        self.checkFt4222Timer.start(self.checkFt4222Delay)
        self.ctrlGpioXTimer = QTimer()
        self.ctrlGpioXTimer.timeout.connect(self.CtrlGpioReversalFun)

    def AutoLinkFt4222Fun(self):
        """
        判断是否需要重新连接串口
        """
        if self.checkBoxAutoLinkFt4222.isChecked() and ("打开设备" in self.btnCtrlFt4222.text()):
            msg = "尝试连接设备······"
            logger.error(msg)
            self.ft4222Signal.emit({"data": msg, "type": 'status_ft4222', })
            self.OpenFt4222Fun()

    def CheckFt4222StatusFun(self):
        """
        监测设备连接状态
        :return:
        """
        self.checkFt4222Timer.stop()
        if self.checkBoxCheckFt4222Status.isChecked():
            self.WatchFt4222Fun()
            if len(self.comboBoxFt4222.currentText()) < 2:
                self.CloseFt4222Fun()
            else:
                self.AutoLinkFt4222Fun()
        self.checkFt4222Timer.start(self.checkFt4222Delay)

    def WatchFt4222Fun(self):
        """
        检测设备
        """
        # 清除旧的设备信息
        self.comboBoxFt4222.clear()
        ft422_list = ft4222.createDeviceInfoList()
        for i in range(ft422_list):
            ft422_info = ft4222.getDeviceInfoDetail(i, False)
            # ft_id = ft422_info.get("id", "")
            ft_desc = ft422_info.get("description", "").decode('utf-8')
            self.comboBoxFt4222.addItem(ft_desc)
            # print(ft422_info)
        if self.comboBoxFt4222.count() <= 0:
            msg = "未检测到设备"
            logger.error(msg)
            self.ft4222Signal.emit({"data": msg, "type": 'status_ft4222', })

    def CloseFt4222Fun(self):
        """
        关闭设备
        """
        try:
            if "Mode 0" in self.operationMode:
                self.myFt4222A.close()
                self.myFt4222B.close()
            elif ("Mode 1" in self.operationMode) or ("Mode 2" in self.operationMode):
                self.myFt4222A.close()
                self.myFt4222B.close()
                self.myFt4222C.close()
                self.myFt4222D.close()
            elif "Mode 3" in self.operationMode:
                self.myFt4222A.close()
        except Exception as err:
            logger.exception(str(err))
            logger.error("设备.close()出错！！")
        msg = "设备已断开！！"
        self.ft4222Signal.emit({"data": msg, "type": 'status_ft4222', })
        logger.error(msg)
        self.btnFt4222Watch.setEnabled(True)
        self.btnCtrlFt4222.setText("打开设备")

    def OpenFt4222Fun(self):
        """
        打开设备
        """
        try:
            self.operationMode = self.comboBoxOperationMode.currentText()
            if "Mode 0" in self.operationMode:
                self.myFt4222A = ft4222.openByDescription("FT4222 A")
                self.myFt4222B = ft4222.openByDescription("FT4222 B")
            elif "Mode 1" in self.operationMode:
                self.myFt4222A = ft4222.openByDescription("FT4222 A")
                self.myFt4222B = ft4222.openByDescription("FT4222 B")
                self.myFt4222C = ft4222.openByDescription("FT4222 C")
                self.myFt4222D = ft4222.openByDescription("FT4222 D")
            elif "Mode 2" in self.operationMode:
                self.myFt4222A = ft4222.openByDescription("FT4222 A")
                self.myFt4222B = ft4222.openByDescription("FT4222 B")
                self.myFt4222C = ft4222.openByDescription("FT4222 C")
                self.myFt4222D = ft4222.openByDescription("FT4222 D")
            elif "Mode 3" in self.operationMode:
                self.myFt4222A = ft4222.openByDescription("FT4222 A")
            # 启用GPIO控制
            if ("Mode 0" in self.operationMode) or ("Mode 1" in self.operationMode):
                # use GPIO2 as gpio (not suspend out)
                self.myFt4222B.setSuspendOut(False)
                # use GPIO3 as gpio (not wakeup)
                self.myFt4222B.setWakeUpInterrupt(False)
                # init GPIO2 as output
                self.myFt4222B.gpio_Init(gpio2=Dir.OUTPUT, gpio3=Dir.INPUT)
            # 启用IIC通信
            if ("Mode 0" in self.operationMode) or ("Mode 3" in self.operationMode):
                curText = self.comboBoxComMode.currentText()
                if "I2C Master" in curText:
                    # init i2c master, clock speed 100kHz
                    self.myFt4222A.i2cMaster_Init(int(self.comboBoxFt4222Speed.currentText()))
            if isinstance(self.myFt4222A, ft4222.FT4222) or isinstance(self.myFt4222B, ft4222.FT4222) or isinstance(
                    self.myFt4222C, ft4222.FT4222) or isinstance(self.myFt4222D, ft4222.FT4222):
                msg = "已连接设备-> FT4222"
                self.ft4222Signal.emit({"data": msg, "type": 'status_ft4222', })
                logger.info(msg)
                self.btnFt4222Watch.setEnabled(False)
                self.btnCtrlFt4222.setText("关闭设备")
                # 接收数据和发送数据数目置零
                self.dataNumReceived = 0
                self.dataNumSended = 0
                self.labelReceNum.setText(str(self.dataNumReceived))
                self.labelSendNum.setText(str(self.dataNumSended))
                self.setWindowTitle(msg)
            else:
                self.CloseFt4222Fun()
        except Exception as err:
            logger.exception(str(err))
            msg = "此设备不能被打开"
            logger.error(msg)
            self.ft4222Signal.emit({"data": msg, "type": 'status_ft4222', })
            self.CloseFt4222Fun()
            return None

    def CtrlFt4222Fun(self):
        """
        按钮控制设备连接
        """
        if "关闭设备" in self.btnCtrlFt4222.text():
            # 断开连接
            self.CloseFt4222Fun()
        else:
            self.OpenFt4222Fun()

    def RecvSignalData(self, dictData):
        """
        接收来自主界面的数据
        :param self:
        :param dictData:
        :return:
        """
        typeValue = dictData.get("type", None)
        dataObj = dictData.get('data', None)
        optionsDict: dict = dictData.get('options', {})
        # 接受Form1传过来的msg，保存到自己的变量里面。
        # print('来自主界面信息：{}，type: {}'.format(dataObj, typeValue))
        if typeValue == 'send_hex':
            data = self.Ft4222_i2cMaster_Write(slaveAddr=dataObj["slaveAddr"], writeData=dataObj["writeData"])
            dataObj = data
        elif (typeValue == 'read_hex') or (typeValue == 'read_ascii'):
            addrLen = optionsDict.get("addrLen", 0)
            if addrLen == 0:
                data = self.Ft4222_i2cMaster_Read(slaveAddr=dataObj["slaveAddr"], dataLen=dataObj["dataLen"])
            else:
                self.ft4222Signal.emit(
                    {"data": binascii.hexlify(dataObj["dataAddr"]).decode('utf-8').upper(), "type": "write->read"})
                data = self.Ft4222_i2cMaster_WR_Read(slaveAddr=dataObj["slaveAddr"], dataAddr=dataObj["dataAddr"],
                                                     dataLen=dataObj["dataLen"])
            dataObj = data
        elif typeValue == 'ctrl_gpio2':
            self.CtrlGpio2Fun(level=dataObj["level"])
        elif typeValue == 'read_gpio':
            self.ReadGpioXFun(portNum=dataObj["portNum"])
        elif typeValue == 'send_ascii':
            self.DataSendFun(data=dataObj, data_type='ascii')
        elif typeValue == 'send_hex':
            self.DataSendFun(data=dataObj, data_type='hex')
        elif typeValue == 'send_protobuf':
            self.DataSendFun(data=dataObj, data_type='protobuf')
        elif typeValue == 'send_data':
            self.DataSendFun(data=dataObj, )
        elif '_to_' in typeValue:
            pass
        else:
            pass
        self.ft4222Signal.emit({"data": dataObj, "type": typeValue, "options": optionsDict})

    def DataSendFun(self, data, data_type=''):
        """
         发送数据
        :param data:
        :param data_type:
        :return:
        """
        if self.myFt4222A:
            pass
        else:
            logger.error("串口已断开！！")
            self.ft4222Signal.emit({"data": "串口已断开", "type": 'status_ft4222', })

    def CtrlGpio2Fun(self, level: bool):
        try:
            self.ctrlGpioXTimer.stop()
            expr = f"self.myFt4222B.gpio_Write(Port.P2, {level})"
            exec(expr)
        except Exception as err:
            msg = str(err)
            logger.warning(msg)
            self.ft4222Signal.emit({"data": msg, "type": 'status_ft4222', })

    def ReadGpioXFun(self, portNum):
        try:
            self.ctrlGpioXTimer.stop()
            expr = f"self.inGpioLevel = self.myFt4222B.gpio_Read(Port.P{portNum})"
            exec(expr)
            self.ft4222Signal.emit({"data": {"portNum": portNum, "level": self.inGpioLevel}, "type": 'level_gpio', })
        except Exception as err:
            msg = str(err)
            logger.warning(msg)
            self.ft4222Signal.emit({"data": msg, "type": 'status_ft4222', })

    def CtrlGpioSingleFun(self):
        try:
            self.ctrlGpioXTimer.stop()
            self.curText = None
            for portX in self.ctrlPortList:
                expr = f"self.curText = self.comboBoxGpio{portX}.currentText().strip()"
                exec(expr)
                if "OUTPUT" in self.curText:
                    expr = f"self.myFt4222B.gpio_Write(Port.P{portX}, self.outGpioLevel)"
                    exec(expr)
                elif "INPUT" in self.curText:
                    expr = f"self.inGpioLevel = self.myFt4222B.gpio_Read(Port.P{portX})"
                    exec(expr)
                    msg = f"GPIO {portX}: {self.inGpioLevel}"
                    # logger.info(msg)
                    self.ft4222Signal.emit({"data": msg, "type": 'status_ft4222', })
            self.outGpioLevel = not self.outGpioLevel
        except Exception as err:
            msg = str(err)
            logger.warning(msg)
            self.ft4222Signal.emit({"data": msg, "type": 'status_ft4222', })

    def CtrlGpioReversalFun(self):
        try:
            self.ctrlGpioXTimer.stop()
            self.curText = None
            for portX in self.ctrlPortList:
                expr = f"self.curText = self.comboBoxGpio{portX}.currentText().strip()"
                exec(expr)
                if "OUTPUT" in self.curText:
                    expr = f"self.myFt4222B.gpio_Write(Port.P{portX}, self.outGpioLevel)"
                    exec(expr)
                elif "INPUT" in self.curText:
                    expr = f"self.inGpioLevel = self.myFt4222B.gpio_Read(Port.P{portX})"
                    exec(expr)
                    msg = f"GPIO {portX}: {self.inGpioLevel}"
                    # logger.info(msg)
                    self.ft4222Signal.emit({"data": msg, "type": 'status_ft4222', })
            self.outGpioLevel = not self.outGpioLevel
            timeDelay = int(self.spinBoxReversal.value())
            if timeDelay > 0:
                self.ctrlGpioXTimer.start(timeDelay)
        except Exception as err:
            msg = str(err)
            logger.warning(msg)
            self.ft4222Signal.emit({"data": msg, "type": 'status_ft4222', })

    def LED1BlinkFun(self):
        try:
            if isinstance(self.myFt4222A, ft4222.FT4222):
                count = 3
                # use GPIO2 as gpio (not suspend out)
                self.myFt4222B.setSuspendOut(False)
                # use GPIO3 as gpio (not wakeup)
                self.myFt4222B.setWakeUpInterrupt(False)
                # init GPIO2 as output
                self.myFt4222B.gpio_Init(gpio2=Dir.OUTPUT, gpio3=Dir.INPUT)
                # generate a square wave signal with GPIO2
                output = True
                while count >= 0:
                    self.myFt4222B.gpio_Write(Port.P2, output)
                    output = not output
                    time.sleep(0.5)
                    count -= 1
                    print(f"GPIO 2: {self.myFt4222B.gpio_Read(Port.P2)} -- count: {count}")
                    print(f"GPIO 3: {self.myFt4222B.gpio_Read(Port.P3)} -- count: {count}")
            else:
                self.CloseFt4222Fun()
        except Exception as err:
            msg = str(err)
            logger.warning(msg)
            self.ft4222Signal.emit({"data": msg, "type": 'status_ft4222', })

    def Ft4222_i2cMaster_Write(self, slaveAddr, writeData):
        """
        #I2C Master写#
        """
        try:
            data = self.myFt4222A.i2cMaster_Write(slaveAddr, writeData)
            return data
        except Exception as err:
            msg = str(err)
            logger.warning(msg)
            self.ft4222Signal.emit({"data": msg, "type": 'status_ft4222', })

    def Ft4222_i2cMaster_Read(self, slaveAddr, dataLen):
        """
        #I2C Master读#
        """
        try:
            slave_data = self.myFt4222A.i2cMaster_Read(slaveAddr, dataLen)
            return slave_data
        except Exception as err:
            msg = str(err)
            logger.warning(msg)
            self.ft4222Signal.emit({"data": msg, "type": 'status_ft4222', })

    def Ft4222_i2cMaster_WR_Read(self, slaveAddr, dataAddr, dataLen):
        """
        #I2C Master读(含一次写时序和一次读时序)#
        """
        writeFlag = 0x00
        if self.checkBoxWrite_START.isChecked():
            writeFlag = writeFlag | ft4222.I2CMaster.Flag.START
        if self.checkBoxWrite_STOP.isChecked():
            writeFlag = writeFlag | ft4222.I2CMaster.Flag.STOP
        if self.checkBoxWrite_REPEATED_START.isChecked():
            writeFlag = writeFlag | ft4222.I2CMaster.Flag.REPEATED_START
        if self.checkBoxWrite_START_AND_STOP.isChecked():
            writeFlag = writeFlag | ft4222.I2CMaster.Flag.START_AND_STOP
        if self.checkBoxWrite_NONE.isChecked():
            writeFlag = writeFlag | ft4222.I2CMaster.Flag.NONE

        readFlag = 0x00
        if self.checkBoxRead_START.isChecked():
            readFlag = readFlag | ft4222.I2CMaster.Flag.START
        if self.checkBoxRead_STOP.isChecked():
            readFlag = readFlag | ft4222.I2CMaster.Flag.STOP
        if self.checkBoxRead_REPEATED_START.isChecked():
            readFlag = readFlag | ft4222.I2CMaster.Flag.REPEATED_START
        if self.checkBoxRead_START_AND_STOP.isChecked():
            readFlag = readFlag | ft4222.I2CMaster.Flag.START_AND_STOP
        if self.checkBoxRead_NONE.isChecked():
            readFlag = readFlag | ft4222.I2CMaster.Flag.NONE

        try:
            self.myFt4222A.i2cMaster_WriteEx(slaveAddr, writeFlag, dataAddr)
            slave_data = self.myFt4222A.i2cMaster_ReadEx(slaveAddr, readFlag, dataLen)
            return slave_data
        except Exception as err:
            msg = str(err)
            logger.warning(msg)
            self.ft4222Signal.emit({"data": msg, "type": 'status_ft4222', })

    def showEvent(self, a0):  # real signature unknown; restored from __doc__
        """ showEvent(self, a0: QShowEvent) """
        msg = "Ft422_Window_Showed"
        logger.debug(msg)
        self.ft4222Signal.emit({"data": msg, "type": 'status_ft4222', })

    def closeEvent(self, a0):  # real signature unknown; restored from __doc__
        """ closeEvent(self, a0: QCloseEvent) """
        msg = "Ft422_Window_Closed"
        logger.debug(msg)
        self.ft4222Signal.emit({"data": msg, "type": 'status_ft4222', })


# Press the green button in the gutter to run the script.
# 程序入口
if __name__ == "__main__":
    app = QApplication(sys.argv)
    ft4222Win = Ft4222Window()
    ft4222Win.show()
    sys.exit(app.exec())

# See PyCharm help at https://www.jetbrains.com/help/pycharm/
